/*
 * Copyright 2016 Google Inc.
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#ifndef SKSL_TYPE
#define SKSL_TYPE

#include "include/core/SkSpan.h"
#include "include/core/SkTypes.h"
#include "include/private/base/SkTArray.h"
#include "src/sksl/SkSLDefines.h"
#include "src/sksl/SkSLPosition.h"
#include "src/sksl/ir/SkSLIRNode.h"
#include "src/sksl/ir/SkSLLayout.h"
#include "src/sksl/ir/SkSLModifierFlags.h"
#include "src/sksl/ir/SkSLSymbol.h"
#include "src/sksl/spirv.h"

#include <cmath>
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <memory>
#include <string>
#include <string_view>
#include <tuple>

namespace SkSL {
class Context;
class Expression;
class SymbolTable;
class Type;

struct CoercionCost {
    static CoercionCost Free()
    {
        return { 0, 0, false };
    }
    static CoercionCost Normal(int cost)
    {
        return { cost, 0, false };
    }
    static CoercionCost Narrowing(int cost)
    {
        return { 0, cost, false };
    }
    static CoercionCost Impossible()
    {
        return { 0, 0, true };
    }

    bool isPossible(bool allowNarrowing) const
    {
        return !fImpossible && (fNarrowingCost == 0 || allowNarrowing);
    }

    // Addition of two costs. Saturates at Impossible().
    CoercionCost operator + (CoercionCost rhs) const
    {
        if (fImpossible || rhs.fImpossible) {
            return Impossible();
        }
        return { fNormalCost + rhs.fNormalCost, fNarrowingCost + rhs.fNarrowingCost, false };
    }

    bool operator < (CoercionCost rhs) const
    {
        return std::tie(fImpossible, fNarrowingCost, fNormalCost) <
            std::tie(rhs.fImpossible, rhs.fNarrowingCost, rhs.fNormalCost);
    }

    bool operator <= (CoercionCost rhs) const
    {
        return std::tie(fImpossible, fNarrowingCost, fNormalCost) <=
            std::tie(rhs.fImpossible, rhs.fNarrowingCost, rhs.fNormalCost);
    }

    int fNormalCost;
    int fNarrowingCost;
    bool fImpossible;
};

/* *
 * Represents a single field in a struct type.
 */
struct Field {
    Field(Position pos, Layout layout, ModifierFlags flags, std::string_view name, const Type *type)
        : fPosition(pos), fLayout(layout), fModifierFlags(flags), fName(name), fType(type)
    {}

    std::string description() const;

    Position fPosition;
    Layout fLayout;
    ModifierFlags fModifierFlags;
    std::string_view fName;
    const Type *fType;
};

/* *
 * Represents a type, such as int or float4.
 */
class Type : public Symbol {
public:
    inline static constexpr Kind kIRNodeKind = Kind::kType;
    inline static constexpr int kMaxAbbrevLength = 3;
    // Represents unspecified array dimensions, as in `int[]`.
    inline static constexpr int kUnsizedArray = -1;

    enum class TypeKind : int8_t {
        kArray,
        kAtomic,
        kGeneric,
        kLiteral,
        kMatrix,
        kOther,
        kSampler,
        kSeparateSampler,
        kScalar,
        kStruct,
        kTexture,
        kVector,
        kVoid,

        // Types that represent stages in the Skia pipeline
        kColorFilter,
        kShader,
        kBlender,
    };

    enum class NumberKind : int8_t { kFloat, kSigned, kUnsigned, kBoolean, kNonnumeric };

    enum class TextureAccess : int8_t {
        kSample, // `kSample` access level allows both sampling and reading
        kRead,
        kWrite,
        kReadWrite,
    };

    Type(const Type &other) = delete;

    /* * Creates an array type. `columns` may be kUnsizedArray. */
    static std::unique_ptr<Type> MakeArrayType(std::string_view name, const Type &componentType, int columns);

    /* * Converts a component type and a size (float, 10) into an array name ("float[10]"). */
    std::string getArrayName(int arraySize) const;

    /* *
     * Creates an alias which maps to another type.
     */
    static std::unique_ptr<Type> MakeAliasType(std::string_view name, const Type &targetType);

    /* *
     * Create a generic type which maps to the listed types--e.g. $genType is a generic type which
     * can match float, float2, float3 or float4.
     */
    static std::unique_ptr<Type> MakeGenericType(const char *name, SkSpan<const Type * const> types,
        const Type *slotType);

    /* * Create a type for literal scalars. */
    static std::unique_ptr<Type> MakeLiteralType(const char *name, const Type &scalarType, int8_t priority);

    /* * Create a matrix type. */
    static std::unique_ptr<Type> MakeMatrixType(std::string_view name, const char *abbrev, const Type &componentType,
        int columns, int8_t rows);

    /* * Create a sampler type. */
    static std::unique_ptr<Type> MakeSamplerType(const char *name, const Type &textureType);

    /* * Create a scalar type. */
    static std::unique_ptr<Type> MakeScalarType(std::string_view name, const char *abbrev, Type::NumberKind numberKind,
        int8_t priority, int8_t bitWidth);

    /* *
     * Create a "special" type with the given name, abbreviation, and TypeKind.
     */
    static std::unique_ptr<Type> MakeSpecialType(const char *name, const char *abbrev, Type::TypeKind typeKind);

    /* *
     * Creates a struct type with the given fields. Reports an error if the struct is not
     * well-formed.
     */
    static std::unique_ptr<Type> MakeStructType(const Context &context, Position pos, std::string_view name,
        skia_private::TArray<Field> fields, bool interfaceBlock = false);

    /* * Create a texture type. */
    static std::unique_ptr<Type> MakeTextureType(const char *name, SpvDim_ dimensions, bool isDepth,
        bool isArrayedTexture, bool isMultisampled, TextureAccess textureAccess);

    /* * Create a vector type. */
    static std::unique_ptr<Type> MakeVectorType(std::string_view name, const char *abbrev, const Type &componentType,
        int columns);

    /* * Create an atomic type. */
    static std::unique_ptr<Type> MakeAtomicType(std::string_view name, const char *abbrev);

    template <typename T> bool is() const
    {
        return this->typeKind() == T::kTypeKind;
    }

    template <typename T> const T &as() const
    {
        SkASSERT(this->is<T>());
        return static_cast<const T &>(*this);
    }

    template <typename T> T &as()
    {
        SkASSERT(this->is<T>());
        return static_cast<T &>(*this);
    }

    /* * Creates a clone of this Type, if needed, and inserts it into a different symbol table. */
    const Type *clone(SymbolTable *symbolTable) const;

    /* *
     * Returns true if this type is known to come from BuiltinTypes. If this returns true, the Type
     * will always be available in the root SymbolTable and never needs to be copied to migrate an
     * Expression from one location to another. If it returns false, the Type might not exist in a
     * separate SymbolTable and you'll need to consider copying it.
     */
    bool isInBuiltinTypes() const
    {
        return !(this->isArray() || this->isStruct());
    }

    std::string displayName() const
    {
        return std::string(this->scalarTypeForLiteral().name());
    }

    std::string description() const override
    {
        return this->displayName();
    }

    /* * Returns true if the program supports this type. Strict ES2 programs can't use ES3 types. */
    bool isAllowedInES2(const Context &context) const;

    /* * Returns true if this type is legal to use in a strict-ES2 program. */
    virtual bool isAllowedInES2() const
    {
        return true;
    }

    /* * If this is an alias, returns the underlying type, otherwise returns this. */
    virtual const Type &resolve() const
    {
        return *this;
    }

    /* * Returns true if these types are equal after alias resolution. */
    bool matches(const Type &other) const
    {
        return this->resolve().name() == other.resolve().name();
    }

    /* *
     * Returns an abbreviated name of the type, meant for name-mangling. (e.g. float4x4 -> f44)
     */
    const char *abbreviatedName() const
    {
        return fAbbreviatedName;
    }

    /* *
     * Returns the category (scalar, vector, matrix, etc.) of this type.
     */
    TypeKind typeKind() const
    {
        return fTypeKind;
    }

    /* *
     * Returns the NumberKind of this type (always kNonnumeric for non-scalar values).
     */
    virtual NumberKind numberKind() const
    {
        return NumberKind::kNonnumeric;
    }

    /* *
     * Returns true if this type is a bool.
     */
    bool isBoolean() const
    {
        return this->numberKind() == NumberKind::kBoolean;
    }

    /* *
     * Returns true if this is a numeric scalar type.
     */
    bool isNumber() const
    {
        switch (this->numberKind()) {
            case NumberKind::kFloat:
            case NumberKind::kSigned:
            case NumberKind::kUnsigned:
                return true;
            default:
                return false;
        }
    }

    /* *
     * Returns true if this is a floating-point scalar type (float or half).
     */
    bool isFloat() const
    {
        return this->numberKind() == NumberKind::kFloat;
    }

    /* *
     * Returns true if this is a signed scalar type (int or short).
     */
    bool isSigned() const
    {
        return this->numberKind() == NumberKind::kSigned;
    }

    /* *
     * Returns true if this is an unsigned scalar type (uint or ushort).
     */
    bool isUnsigned() const
    {
        return this->numberKind() == NumberKind::kUnsigned;
    }

    /* *
     * Returns true if this is a signed or unsigned integer.
     */
    bool isInteger() const
    {
        switch (this->numberKind()) {
            case NumberKind::kSigned:
            case NumberKind::kUnsigned:
                return true;
            default:
                return false;
        }
    }

    /* *
     * Returns true if this is an "opaque type" (an external object which the shader references in
     * some fashion). https://www.khronos.org/opengl/wiki/Data_Type_(GLSL)#Opaque_types
     */
    bool isOpaque() const
    {
        switch (fTypeKind) {
            case TypeKind::kAtomic:
            case TypeKind::kBlender:
            case TypeKind::kColorFilter:
            case TypeKind::kSampler:
            case TypeKind::kSeparateSampler:
            case TypeKind::kShader:
            case TypeKind::kTexture:
                return true;
            default:
                return false;
        }
    }

    /* *
     * Returns true if this is a storage texture.
     */
    bool isStorageTexture() const
    {
        return fTypeKind == TypeKind::kTexture && this->dimensions() != SpvDimSubpassData;
    }

    /* *
     * Returns the "priority" of a number type, in order of float > half > int > short.
     * When operating on two number types, the result is the higher-priority type.
     */
    virtual int priority() const
    {
        SkDEBUGFAIL("not a number type");
        return -1;
    }

    /* *
     * Returns true if an instance of this type can be freely coerced (implicitly converted) to
     * another type.
     */
    bool canCoerceTo(const Type &other, bool allowNarrowing) const
    {
        return this->coercionCost(other).isPossible(allowNarrowing);
    }

    /* *
     * Determines the "cost" of coercing (implicitly converting) this type to another type. The cost
     * is a number with no particular meaning other than that lower costs are preferable to higher
     * costs. Returns INT_MAX if the coercion is not possible.
     */
    CoercionCost coercionCost(const Type &other) const;

    /* *
     * For matrices and vectors, returns the type of individual cells (e.g. mat2 has a component
     * type of Float). For arrays, returns the base type. For all other types, returns the type
     * itself.
     */
    virtual const Type &componentType() const
    {
        return *this;
    }

    /* *
     * For matrix types, returns the type of a single column (`m[n]`). Asserts for all other types.
     */
    const Type &columnType(const Context &context) const
    {
        return this->componentType().toCompound(context, this->rows(), /* rows= */ 1);
    }

    /* *
     * For texture samplers, returns the type of texture it samples (e.g., sampler2D has
     * a texture type of texture2D).
     */
    virtual const Type &textureType() const
    {
        SkDEBUGFAIL("not a sampler type");
        return *this;
    }

    /* *
     * For matrices and vectors, returns the number of columns (e.g. both mat3 and float3 return 3).
     * For scalars, returns 1. For arrays, returns either the size of the array (if known) or -1.
     * For all other types, causes an assertion failure.
     */
    virtual int columns() const
    {
        SkDEBUGFAIL("type does not have columns");
        return -1;
    }

    /* *
     * For matrices, returns the number of rows (e.g. mat2x4 returns 4). For vectors and scalars,
     * returns 1. For all other types, causes an assertion failure.
     */
    virtual int rows() const
    {
        SkDEBUGFAIL("type does not have rows");
        return -1;
    }

    /* * Returns the minimum value that can fit in the type. */
    virtual double minimumValue() const
    {
        SkDEBUGFAIL("type does not have a minimum value");
        return -INFINITY;
    }

    virtual double maximumValue() const
    {
        SkDEBUGFAIL("type does not have a maximum value");
        return +INFINITY;
    }

    /* *
     * Returns the number of scalars needed to hold this type.
     */
    virtual size_t slotCount() const
    {
        return 0;
    }

    /* *
     * Returns the type of the value in the nth slot. For scalar, vector and matrix types, should
     * always match `componentType()`.
     */
    virtual const Type &slotType(size_t) const
    {
        return *this;
    }

    virtual SkSpan<const Field> fields() const
    {
        SK_ABORT("Internal error: not a struct");
    }

    /* *
     * For generic types, returns the types that this generic type can substitute for.
     */
    virtual SkSpan<const Type * const> coercibleTypes() const
    {
        SkDEBUGFAIL("Internal error: not a generic type");
        return {};
    }

    virtual SpvDim_ dimensions() const
    {
        SkDEBUGFAIL("Internal error: not a texture type");
        return SpvDim1D;
    }

    virtual bool isDepth() const
    {
        SkDEBUGFAIL("Internal error: not a texture type");
        return false;
    }

    virtual bool isArrayedTexture() const
    {
        SkDEBUGFAIL("Internal error: not a texture type");
        return false;
    }

    bool isVoid() const
    {
        return fTypeKind == TypeKind::kVoid;
    }

    bool isGeneric() const
    {
        return fTypeKind == TypeKind::kGeneric;
    }

    bool isSampler() const
    {
        return fTypeKind == TypeKind::kSampler;
    }

    bool isAtomic() const
    {
        return this->typeKind() == TypeKind::kAtomic;
    }

    virtual bool isScalar() const
    {
        return false;
    }

    virtual bool isLiteral() const
    {
        return false;
    }

    virtual const Type &scalarTypeForLiteral() const
    {
        return *this;
    }

    virtual bool isVector() const
    {
        return false;
    }

    virtual bool isMatrix() const
    {
        return false;
    }

    virtual bool isArray() const
    {
        return false;
    }

    virtual bool isUnsizedArray() const
    {
        return false;
    }

    virtual bool isStruct() const
    {
        return false;
    }

    virtual bool isInterfaceBlock() const
    {
        return false;
    }

    // Is this type something that can be bound & sampled from an SkRuntimeEffect?
    // Includes types that represent stages of the Skia pipeline (colorFilter, shader, blender).
    bool isEffectChild() const
    {
        return fTypeKind == TypeKind::kColorFilter || fTypeKind == TypeKind::kShader || fTypeKind == TypeKind::kBlender;
    }

    virtual bool isMultisampled() const
    {
        SkDEBUGFAIL("not a texture type");
        return false;
    }

    virtual TextureAccess textureAccess() const
    {
        SkDEBUGFAIL("not a texture type");
        return TextureAccess::kSample;
    }

    bool hasPrecision() const
    {
        return this->componentType().isNumber() || this->isSampler();
    }

    bool highPrecision() const
    {
        return this->bitWidth() >= 32;
    }

    virtual int bitWidth() const
    {
        return 0;
    }

    virtual bool isOrContainsArray() const
    {
        return false;
    }

    virtual bool isOrContainsUnsizedArray() const
    {
        return false;
    }

    virtual bool isOrContainsAtomic() const
    {
        return false;
    }

    /* *
     * Returns the corresponding vector or matrix type with the specified number of columns and
     * rows.
     */
    const Type &toCompound(const Context &context, int columns, int rows) const;

    /* *
     * Returns a type which honors the precision and access-level qualifiers set in ModifierFlags.
     * For example:
     * - Modifier `mediump` + Type `float2`:     Type `half2`
     * - Modifier `readonly` + Type `texture2D`: Type `readonlyTexture2D`
     * Generates an error if the qualifiers don't make sense (`highp bool`, `writeonly MyStruct`)
     */
    const Type *applyQualifiers(const Context &context, ModifierFlags *modifierFlags, Position pos) const;

    /* *
     * Coerces the passed-in expression to this type. If the types are incompatible, reports an
     * error and returns null.
     */
    std::unique_ptr<Expression> coerceExpression(std::unique_ptr<Expression> expr, const Context &context) const;

    /* * Detects any IntLiterals in the expression which can't fit in this type. */
    bool checkForOutOfRangeLiteral(const Context &context, const Expression &expr) const;

    /* * Checks if `value` can fit in this type. The type must be scalar. */
    bool checkForOutOfRangeLiteral(const Context &context, double value, Position pos) const;

    /* *
     * Reports errors and returns false if this type cannot be used as the base type for an array.
     */
    bool checkIfUsableInArray(const Context &context, Position arrayPos) const;

    /* *
     * Verifies that the expression is a valid constant array size for this type. Returns the array
     * size, or reports errors and returns zero if the expression isn't a valid literal value.
     */
    SKSL_INT convertArraySize(const Context &context, Position arrayPos, std::unique_ptr<Expression> size) const;

    SKSL_INT convertArraySize(const Context &context, Position arrayPos, Position sizePos, SKSL_INT size) const;

protected:
    Type(std::string_view name, const char *abbrev, TypeKind kind, Position pos = Position())
        : INHERITED(pos, kIRNodeKind, name), fTypeKind(kind)
    {
        SkASSERT(strlen(abbrev) <= kMaxAbbrevLength);
        strcpy(fAbbreviatedName, abbrev);
    }

    const Type *applyPrecisionQualifiers(const Context &context, ModifierFlags *modifierFlags, Position pos) const;

    const Type *applyAccessQualifiers(const Context &context, ModifierFlags *modifierFlags, Position pos) const;

    /* * If the type is a struct, returns the depth of the struct's most deeply-nested field. */
    virtual int structNestingDepth() const
    {
        return 0;
    }

private:
    using INHERITED = Symbol;

    char fAbbreviatedName[kMaxAbbrevLength + 1] = {};
    TypeKind fTypeKind;
};
} // namespace SkSL

#endif
